package com.huxi.lang.object.support;

import java.lang.invoke.MethodHandle;
import java.util.List;
import java.util.Objects;

import com.huxi.lang.object.VField;
import com.huxi.lang.validation.predicate.VPredicate;

public abstract class ReflectField implements VField<Object> {

	private final String field;
	private final MethodHandle getter;
	private String format;
	private String displayName;
	private String describe;
	private String message;
	private VPredicate predicate;

	protected ReflectField(String field, MethodHandle getter) {
		this.field = Objects.requireNonNull(field);
		this.getter = getter;
	}

	public String getFormat() {
		return format;
	}

	public String getDisplayName() {
		return displayName;
	}

	public String getDescribe() {
		return describe;
	}

	public String getMessage() {
		return message;
	}

	public VPredicate getPredicate() {
		return predicate;
	}

	ReflectField withFormat(String format) {
		this.format = format;
		return this;
	}

	ReflectField withDisplayName(String displayName) {
		this.displayName = displayName;
		return this;
	}

	ReflectField withDescribe(String describe) {
		this.describe = describe;
		return this;
	}

	ReflectField withMessage(String message) {
		this.message = message;
		return this;
	}

	ReflectField withPredicate(VPredicate predicate) {
		this.predicate = predicate;
		return this;
	}

	protected abstract MethodHandle findSetter(Class<?> valueType);

	@Override
	public String getField() {
		return field;
	}

	public boolean readable() {
		return getter != null;
	}

	@Override
	public Object get(Object vmap) {
		if (getter == null) {
			throw new UnsupportedOperationException("field not readable");
		}
		try {
			return getter.invokeExact(vmap);
		} catch (Throwable th) {
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			}
			throw new RuntimeException(th);
		}
	}

	public boolean writable() {
		return findSetter(null) != null;
	}

	@Override
	public Object set(Object vmap, Object value) {
		try {
			MethodHandle setter = findSetter(value != null ? value.getClass() : null);
			setter.invoke(vmap, value);
			return null;
		} catch (Throwable th) {
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			}
			throw new RuntimeException(th);
		}
	}

	public static class ExactField extends ReflectField {
		private final MethodHandle setter;

		ExactField(String field, MethodHandle getter, MethodHandle setter) {
			super(field, getter);
			this.setter = setter;
		}

		@Override
		protected MethodHandle findSetter(Class<?> valueType) {
			return setter;
		}
	}

	public static class RouteField extends ReflectField {
		private final MethodHandle setter;
		private final List<MethodHandle> routes;

		RouteField(String field, MethodHandle getter, MethodHandle setter, List<MethodHandle> routes) {
			super(field, getter);
			this.setter = setter;
			this.routes = routes;
		}

		@Override
		protected MethodHandle findSetter(Class<?> valueType) {
			if (!(routes.size() == 0 || valueType == null)) {
				MethodHandle h = setter;
				int c = getObjectTransformationCost(valueType, h.type().parameterType(1));
				for (MethodHandle m : routes) {
					int v = getObjectTransformationCost(valueType, m.type().parameterType(1));
					if (v < c) {
						c = v;
						h = m;
					}
				}
				return h;
			}
			return setter;
		}
	}

	public static int getObjectTransformationCost(Class<?> sourType, Class<?> destType) {
		if (sourType == destType) {
			return 0;
		}
		int cost = 1;
		if (destType.isPrimitive()) {

		} else {
			cost <<= 1;
			if (destType.isAssignableFrom(sourType)) {
				return cost;
			}
		}
		return cost << 1;
	}

}
